// @file PlatQt.cpp
//          Copyright (c) 1990-2011, Scientific Toolworks, Inc.
//
// The License.txt file describes the conditions under which this software may be distributed.
//
// Author: Jason Haslam
//
// Additions Copyright (c) 2011 Archaeopteryx Software, Inc. d/b/a Wingware
// Scintilla platform layer for Qt

#include <cstdio>

#include "PlatQt.h"
#include "Scintilla.h"
#include "XPM.h"
#include "UniConversion.h"
#include "DBCS.h"

#include <QApplication>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QScreen>
#endif
#include <QFont>
#include <QColor>
#include <QRect>
#include <QPaintDevice>
#include <QPaintEngine>
#include <QWidget>
#include <QPixmap>
#include <QPainter>
#include <QPainterPath>
#include <QMenu>
#include <QAction>
#include <QTime>
#include <QMessageBox>
#include <QListWidget>
#include <QVarLengthArray>
#include <QScrollBar>
#include <QTextLayout>
#include <QTextLine>
#include <QLibrary>

using namespace Scintilla;

namespace Scintilla::Internal {
	//----------------------------------------------------------------------

	// Convert from a Scintilla characterSet value to a Qt codec name.
	const char* CharacterSetID(CharacterSet characterSet)
	{
		switch (characterSet) {
			//case CharacterSet::Ansi:
			//	return "";
		case CharacterSet::Default:
			return "ISO 8859-1";
		case CharacterSet::Baltic:
			return "ISO 8859-13";
		case CharacterSet::ChineseBig5:
			return "Big5";
		case CharacterSet::EastEurope:
			return "ISO 8859-2";
		case CharacterSet::GB2312:
			return "GB18030-0";
		case CharacterSet::Greek:
			return "ISO 8859-7";
		case CharacterSet::Hangul:
			return "CP949";
		case CharacterSet::Mac:
			return "Apple Roman";
			//case SC_CHARSET_OEM:
			//	return "ASCII";
		case CharacterSet::Russian:
			return "KOI8-R";
		case CharacterSet::Cyrillic:
			return "Windows-1251";
		case CharacterSet::ShiftJis:
			return "Shift-JIS";
			//case SC_CHARSET_SYMBOL:
			//        return "";
		case CharacterSet::Turkish:
			return "ISO 8859-9";
			//case SC_CHARSET_JOHAB:
			//        return "CP1361";
		case CharacterSet::Hebrew:
			return "ISO 8859-8";
		case CharacterSet::Arabic:
			return "ISO 8859-6";
		case CharacterSet::Vietnamese:
			return "Windows-1258";
		case CharacterSet::Thai:
			return "TIS-620";
		case CharacterSet::Iso8859_15:
			return "ISO 8859-15";
		default:
			return "ISO 8859-1";
		}
	}

	QString UnicodeFromText(QTextCodec* codec, std::string_view text) {
		return codec->toUnicode(text.data(), static_cast<int>(text.length()));
	}

	static QFont::StyleStrategy ChooseStrategy(FontQuality eff)
	{
		switch (eff) {
		case FontQuality::QualityDefault:         return QFont::PreferDefault;
		case FontQuality::QualityNonAntialiased: return QFont::NoAntialias;
		case FontQuality::QualityAntialiased:     return QFont::PreferAntialias;
		case FontQuality::QualityLcdOptimized:   return QFont::PreferAntialias;
		default:                             return QFont::PreferDefault;
		}
	}

	static QFont::Stretch QStretchFromFontStretch(Scintilla::FontStretch stretch)
	{
		switch (stretch) {
		case FontStretch::UltraCondensed:
			return QFont::Stretch::UltraCondensed;
		case FontStretch::ExtraCondensed:
			return QFont::Stretch::ExtraCondensed;
		case FontStretch::Condensed:
			return QFont::Stretch::Condensed;
		case FontStretch::SemiCondensed:
			return QFont::Stretch::SemiCondensed;
		case FontStretch::Normal:
			return QFont::Stretch::Unstretched;
		case FontStretch::SemiExpanded:
			return QFont::Stretch::SemiExpanded;
		case FontStretch::Expanded:
			return QFont::Stretch::Expanded;
		case FontStretch::ExtraExpanded:
			return QFont::Stretch::ExtraExpanded;
		case FontStretch::UltraExpanded:
			return QFont::Stretch::UltraExpanded;
		default:
			return QFont::Stretch::Unstretched;
		}
	}

	class FontAndCharacterSet : public Font {
	public:
		CharacterSet characterSet = CharacterSet::Ansi;
		std::unique_ptr<QFont> pfont;
		explicit FontAndCharacterSet(const FontParameters& fp) : characterSet(fp.characterSet) {
			pfont = std::make_unique<QFont>();
			pfont->setStyleStrategy(ChooseStrategy(fp.extraFontFlag));
			pfont->setFamily(QString::fromUtf8(fp.faceName));
			pfont->setPointSizeF(fp.size);
			pfont->setBold(static_cast<int>(fp.weight) > 500);
			pfont->setStretch(QStretchFromFontStretch(fp.stretch));
			pfont->setItalic(fp.italic);
		}
	};

	namespace {
		const Supports SupportsQt[] = {
			Supports::LineDrawsFinal,
			Supports::FractionalStrokeWidth,
			Supports::TranslucentStroke,
			Supports::PixelModification,
		};

		const FontAndCharacterSet* AsFontAndCharacterSet(const Font* f) {
			return dynamic_cast<const FontAndCharacterSet*>(f);
		}

		QFont* FontPointer(const Font* f)
		{
			return AsFontAndCharacterSet(f)->pfont.get();
		}
	}

	std::shared_ptr<Font> Font::Allocate(const FontParameters& fp)
	{
		return std::make_shared<FontAndCharacterSet>(fp);
	}

	SurfaceImpl::SurfaceImpl() = default;

	SurfaceImpl::SurfaceImpl(int width, int height, SurfaceMode mode_)
	{
		if (width < 1) width = 1;
		if (height < 1) height = 1;
		deviceOwned = true;
		device = new QPixmap(width, height);
		mode = mode_;
	}

	SurfaceImpl::~SurfaceImpl()
	{
		Clear();
	}

	void SurfaceImpl::Clear()
	{
		if (painterOwned && painter) {
			delete painter;
		}

		if (deviceOwned && device) {
			delete device;
		}
		device = nullptr;
		painter = nullptr;
		deviceOwned = false;
		painterOwned = false;
	}

	void SurfaceImpl::Init(WindowID wid)
	{
		Release();
		device = static_cast<QWidget*>(wid);
	}

	void SurfaceImpl::Init(SurfaceID sid, WindowID /*wid*/)
	{
		Release();
		device = static_cast<QPaintDevice*>(sid);
	}

	std::unique_ptr<Surface> SurfaceImpl::AllocatePixMap(int width, int height)
	{
		return std::make_unique<SurfaceImpl>(width, height, mode);
	}

	void SurfaceImpl::SetMode(SurfaceMode mode_)
	{
		mode = mode_;
	}

	void SurfaceImpl::Release() noexcept
	{
		Clear();
	}

	int SurfaceImpl::SupportsFeature(Supports feature) noexcept
	{
		for (const Supports f : SupportsQt) {
			if (f == feature)
				return 1;
		}
		return 0;
	}

	bool SurfaceImpl::Initialised()
	{
		return device != nullptr;
	}

	void SurfaceImpl::PenColour(ColourRGBA fore)
	{
		QPen penOutline(QColorFromColourRGBA(fore));
		penOutline.setCapStyle(Qt::FlatCap);
		GetPainter()->setPen(penOutline);
	}

	void SurfaceImpl::PenColourWidth(ColourRGBA fore, XYPOSITION strokeWidth) {
		QPen penOutline(QColorFromColourRGBA(fore));
		penOutline.setCapStyle(Qt::FlatCap);
		penOutline.setJoinStyle(Qt::MiterJoin);
		penOutline.setWidthF(strokeWidth);
		GetPainter()->setPen(penOutline);
	}

	void SurfaceImpl::BrushColour(ColourRGBA back)
	{
		GetPainter()->setBrush(QBrush(QColorFromColourRGBA(back)));
	}

	void SurfaceImpl::SetCodec(const Font* font)
	{
		const FontAndCharacterSet* pfacs = AsFontAndCharacterSet(font);
		if (pfacs && pfacs->pfont) {
			const char* csid = "UTF-8";
			if (!(mode.codePage == SC_CP_UTF8))
				csid = CharacterSetID(pfacs->characterSet);
			if (csid != codecName) {
				codecName = csid;
				codec = QTextCodec::codecForName(csid);
			}
		}
	}

	void SurfaceImpl::SetFont(const Font* font)
	{
		const FontAndCharacterSet* pfacs = AsFontAndCharacterSet(font);
		if (pfacs && pfacs->pfont) {
			GetPainter()->setFont(*(pfacs->pfont));
			SetCodec(font);
		}
	}

	int SurfaceImpl::LogPixelsY()
	{
		return device->logicalDpiY();
	}

	int SurfaceImpl::PixelDivisions()
	{
		// Qt uses device pixels.
		return 1;
	}

	int SurfaceImpl::DeviceHeightFont(int points)
	{
		return points;
	}

	void SurfaceImpl::LineDraw(Point start, Point end, Stroke stroke)
	{
		PenColourWidth(stroke.colour, stroke.width);
		QLineF line(start.x, start.y, end.x, end.y);
		GetPainter()->drawLine(line);
	}

	void SurfaceImpl::PolyLine(const Point* pts, size_t npts, Stroke stroke)
	{
		// TODO: set line joins and caps
		PenColourWidth(stroke.colour, stroke.width);
		std::vector<QPointF> qpts;
		std::transform(pts, pts + npts, std::back_inserter(qpts), QPointFFromPoint);
		GetPainter()->drawPolyline(&qpts[0], static_cast<int>(npts));
	}

	void SurfaceImpl::Polygon(const Point* pts, size_t npts, FillStroke fillStroke)
	{
		PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
		BrushColour(fillStroke.fill.colour);

		std::vector<QPointF> qpts;
		std::transform(pts, pts + npts, std::back_inserter(qpts), QPointFFromPoint);

		GetPainter()->drawPolygon(&qpts[0], static_cast<int>(npts));
	}

	void SurfaceImpl::RectangleDraw(PRectangle rc, FillStroke fillStroke)
	{
		PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
		BrushColour(fillStroke.fill.colour);
		const QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2));
		GetPainter()->drawRect(rect);
	}

	void SurfaceImpl::RectangleFrame(PRectangle rc, Stroke stroke) {
		PenColourWidth(stroke.colour, stroke.width);
		// Default QBrush is Qt::NoBrush so does not fill
		GetPainter()->setBrush(QBrush());
		const QRectF rect = QRectFFromPRect(rc.Inset(stroke.width / 2));
		GetPainter()->drawRect(rect);
	}

	void SurfaceImpl::FillRectangle(PRectangle rc, Fill fill)
	{
		GetPainter()->fillRect(QRectFFromPRect(rc), QColorFromColourRGBA(fill.colour));
	}

	void SurfaceImpl::FillRectangleAligned(PRectangle rc, Fill fill)
	{
		FillRectangle(PixelAlign(rc, 1), fill);
	}

	void SurfaceImpl::FillRectangle(PRectangle rc, Surface& surfacePattern)
	{
		// Tile pattern over rectangle
		SurfaceImpl* surface = dynamic_cast<SurfaceImpl*>(&surfacePattern);
		const QPixmap* pixmap = static_cast<QPixmap*>(surface->GetPaintDevice());
		GetPainter()->drawTiledPixmap(QRectFromPRect(rc), *pixmap);
	}

	void SurfaceImpl::RoundedRectangle(PRectangle rc, FillStroke fillStroke)
	{
		PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
		BrushColour(fillStroke.fill.colour);
		GetPainter()->drawRoundedRect(QRectFFromPRect(rc), 3.0f, 3.0f);
	}

	void SurfaceImpl::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke)
	{
		QColor qFill = QColorFromColourRGBA(fillStroke.fill.colour);
		QBrush brushFill(qFill);
		GetPainter()->setBrush(brushFill);
		if (fillStroke.fill.colour == fillStroke.stroke.colour) {
			painter->setPen(Qt::NoPen);
			QRectF rect = QRectFFromPRect(rc);
			if (cornerSize > 0.0f) {
				// A radius of 1 shows no curve so add 1
				qreal radius = cornerSize + 1;
				GetPainter()->drawRoundedRect(rect, radius, radius);
			}
			else {
				GetPainter()->fillRect(rect, brushFill);
			}
		}
		else {
			QColor qOutline = QColorFromColourRGBA(fillStroke.stroke.colour);
			QPen penOutline(qOutline);
			penOutline.setWidthF(fillStroke.stroke.width);
			GetPainter()->setPen(penOutline);

			QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2));
			if (cornerSize > 0.0f) {
				// A radius of 1 shows no curve so add 1
				qreal radius = cornerSize + 1;
				GetPainter()->drawRoundedRect(rect, radius, radius);
			}
			else {
				GetPainter()->drawRect(rect);
			}
		}
	}

	void SurfaceImpl::GradientRectangle(PRectangle rc, const std::vector<ColourStop>& stops, GradientOptions options) {
		QRectF rect = QRectFFromPRect(rc);
		QLinearGradient linearGradient;
		switch (options) {
		case GradientOptions::leftToRight:
			linearGradient = QLinearGradient(rc.left, rc.top, rc.right, rc.top);
			break;
		case GradientOptions::topToBottom:
		default:
			linearGradient = QLinearGradient(rc.left, rc.top, rc.left, rc.bottom);
			break;
		}
		linearGradient.setSpread(QGradient::RepeatSpread);
		for (const ColourStop& stop : stops) {
			linearGradient.setColorAt(stop.position, QColorFromColourRGBA(stop.colour));
		}
		QBrush brush = QBrush(linearGradient);
		GetPainter()->fillRect(rect, brush);
	}

	static std::vector<unsigned char> ImageByteSwapped(int width, int height, const unsigned char* pixelsImage)
	{
		// Input is RGBA, but Format_ARGB32 is BGRA, so swap the red bytes and blue bytes
		size_t bytes = width * height * 4;
		std::vector<unsigned char> imageBytes(pixelsImage, pixelsImage + bytes);
		for (size_t i = 0; i < bytes; i += 4)
			std::swap(imageBytes[i], imageBytes[i + 2]);
		return imageBytes;
	}

	void SurfaceImpl::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char* pixelsImage)
	{
		std::vector<unsigned char> imageBytes = ImageByteSwapped(width, height, pixelsImage);
		QImage image(&imageBytes[0], width, height, QImage::Format_ARGB32);
		QPoint pt(rc.left, rc.top);
		GetPainter()->drawImage(pt, image);
	}

	void SurfaceImpl::Ellipse(PRectangle rc, FillStroke fillStroke)
	{
		PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
		BrushColour(fillStroke.fill.colour);
		const QRectF rect = QRectFFromPRect(rc.Inset(fillStroke.stroke.width / 2));
		GetPainter()->drawEllipse(rect);
	}

	void SurfaceImpl::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) {
		const XYPOSITION halfStroke = fillStroke.stroke.width / 2.0f;
		const XYPOSITION radius = rc.Height() / 2.0f - halfStroke;
		PRectangle rcInner = rc;
		rcInner.left += radius;
		rcInner.right -= radius;
		const XYPOSITION arcHeight = rc.Height() - fillStroke.stroke.width;

		PenColourWidth(fillStroke.stroke.colour, fillStroke.stroke.width);
		BrushColour(fillStroke.fill.colour);

		QPainterPath path;

		const Ends leftSide = static_cast<Ends>(static_cast<unsigned int>(ends) & 0xfu);
		const Ends rightSide = static_cast<Ends>(static_cast<unsigned int>(ends) & 0xf0u);
		switch (leftSide) {
		case Ends::leftFlat:
			path.moveTo(rc.left + halfStroke, rc.top + halfStroke);
			path.lineTo(rc.left + halfStroke, rc.bottom - halfStroke);
			break;
		case Ends::leftAngle:
			path.moveTo(rcInner.left + halfStroke, rc.top + halfStroke);
			path.lineTo(rc.left + halfStroke, rc.Centre().y);
			path.lineTo(rcInner.left + halfStroke, rc.bottom - halfStroke);
			break;
		case Ends::semiCircles:
		default:
			path.moveTo(rcInner.left + halfStroke, rc.top + halfStroke);
			QRectF rectangleArc(rc.left + halfStroke, rc.top + halfStroke,
				arcHeight, arcHeight);
			path.arcTo(rectangleArc, 90, 180);
			break;
		}

		switch (rightSide) {
		case Ends::rightFlat:
			path.lineTo(rc.right - halfStroke, rc.bottom - halfStroke);
			path.lineTo(rc.right - halfStroke, rc.top + halfStroke);
			break;
		case Ends::rightAngle:
			path.lineTo(rcInner.right - halfStroke, rc.bottom - halfStroke);
			path.lineTo(rc.right - halfStroke, rc.Centre().y);
			path.lineTo(rcInner.right - halfStroke, rc.top + halfStroke);
			break;
		case Ends::semiCircles:
		default:
			path.lineTo(rcInner.right - halfStroke, rc.bottom - halfStroke);
			QRectF rectangleArc(rc.right - arcHeight - halfStroke, rc.top + halfStroke,
				arcHeight, arcHeight);
			path.arcTo(rectangleArc, 270, 180);
			break;
		}

		// Close the path to enclose it for stroking and for filling, then draw it
		path.closeSubpath();
		GetPainter()->drawPath(path);
	}

	void SurfaceImpl::Copy(PRectangle rc, Point from, Surface& surfaceSource)
	{
		SurfaceImpl* source = dynamic_cast<SurfaceImpl*>(&surfaceSource);
		QPixmap* pixmap = static_cast<QPixmap*>(source->GetPaintDevice());

		GetPainter()->drawPixmap(rc.left, rc.top, *pixmap, from.x, from.y, -1, -1);
	}

	std::unique_ptr<IScreenLineLayout> SurfaceImpl::Layout(const IScreenLine*)
	{
		return {};
	}

	void SurfaceImpl::DrawTextNoClip(PRectangle rc,
		const Font* font,
		XYPOSITION ybase,
		std::string_view text,
		ColourRGBA fore,
		ColourRGBA back)
	{
		SetFont(font);
		PenColour(fore);

		GetPainter()->setBackground(QColorFromColourRGBA(back));
		GetPainter()->setBackgroundMode(Qt::OpaqueMode);
		QString su = UnicodeFromText(codec, text);
		GetPainter()->drawText(QPointF(rc.left, ybase), su);
	}

	void SurfaceImpl::DrawTextClipped(PRectangle rc,
		const Font* font,
		XYPOSITION ybase,
		std::string_view text,
		ColourRGBA fore,
		ColourRGBA back)
	{
		SetClip(rc);
		DrawTextNoClip(rc, font, ybase, text, fore, back);
		PopClip();
	}

	void SurfaceImpl::DrawTextTransparent(PRectangle rc,
		const Font* font,
		XYPOSITION ybase,
		std::string_view text,
		ColourRGBA fore)
	{
		SetFont(font);
		PenColour(fore);

		GetPainter()->setBackgroundMode(Qt::TransparentMode);
		QString su = UnicodeFromText(codec, text);
		GetPainter()->drawText(QPointF(rc.left, ybase), su);
	}

	void SurfaceImpl::SetClip(PRectangle rc)
	{
		GetPainter()->save();
		GetPainter()->setClipRect(QRectFFromPRect(rc), Qt::IntersectClip);
	}

	void SurfaceImpl::PopClip()
	{
		GetPainter()->restore();
	}

	void SurfaceImpl::MeasureWidths(const Font* font,
		std::string_view text,
		XYPOSITION* positions)
	{
		if (!font)
			return;
		SetCodec(font);
		QString su = UnicodeFromText(codec, text);
		QTextLayout tlay(su, *FontPointer(font), GetPaintDevice());
		tlay.beginLayout();
		QTextLine tl = tlay.createLine();
		tlay.endLayout();
		if (mode.codePage == SC_CP_UTF8) {
			int fit = su.size();
			int ui = 0;
			size_t i = 0;
			while (ui < fit) {
				const unsigned char uch = text[i];
				const unsigned int byteCount = UTF8BytesOfLead[uch];
				const int codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
				qreal xPosition = tl.cursorToX(ui + codeUnits);
				for (size_t bytePos = 0; (bytePos < byteCount) && (i < text.length()); bytePos++) {
					positions[i++] = xPosition;
				}
				ui += codeUnits;
			}
			XYPOSITION lastPos = 0;
			if (i > 0)
				lastPos = positions[i - 1];
			while (i < text.length()) {
				positions[i++] = lastPos;
			}
		}
		else if (mode.codePage) {
			// DBCS
			int ui = 0;
			for (size_t i = 0; i < text.length();) {
				size_t lenChar = DBCSIsLeadByte(mode.codePage, text[i]) ? 2 : 1;
				qreal xPosition = tl.cursorToX(ui + 1);
				for (unsigned int bytePos = 0; (bytePos < lenChar) && (i < text.length()); bytePos++) {
					positions[i++] = xPosition;
				}
				ui++;
			}
		}
		else {
			// Single byte encoding
			for (int i = 0; i < static_cast<int>(text.length()); i++) {
				positions[i] = tl.cursorToX(i + 1);
			}
		}
	}

	XYPOSITION SurfaceImpl::WidthText(const Font* font, std::string_view text)
	{
		QFontMetricsF metrics(*FontPointer(font), device);
		SetCodec(font);
		QString su = UnicodeFromText(codec, text);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
		return metrics.horizontalAdvance(su);
#else
		return metrics.width(su);
#endif
	}

	void SurfaceImpl::DrawTextNoClipUTF8(PRectangle rc,
		const Font* font,
		XYPOSITION ybase,
		std::string_view text,
		ColourRGBA fore,
		ColourRGBA back)
	{
		SetFont(font);
		PenColour(fore);

		GetPainter()->setBackground(QColorFromColourRGBA(back));
		GetPainter()->setBackgroundMode(Qt::OpaqueMode);
		QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
		GetPainter()->drawText(QPointF(rc.left, ybase), su);
	}

	void SurfaceImpl::DrawTextClippedUTF8(PRectangle rc,
		const Font* font,
		XYPOSITION ybase,
		std::string_view text,
		ColourRGBA fore,
		ColourRGBA back)
	{
		SetClip(rc);
		DrawTextNoClip(rc, font, ybase, text, fore, back);
		PopClip();
	}

	void SurfaceImpl::DrawTextTransparentUTF8(PRectangle rc,
		const Font* font,
		XYPOSITION ybase,
		std::string_view text,
		ColourRGBA fore)
	{
		SetFont(font);
		PenColour(fore);

		GetPainter()->setBackgroundMode(Qt::TransparentMode);
		QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
		GetPainter()->drawText(QPointF(rc.left, ybase), su);
	}

	void SurfaceImpl::MeasureWidthsUTF8(const Font* font,
		std::string_view text,
		XYPOSITION* positions)
	{
		if (!font)
			return;
		QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
		QTextLayout tlay(su, *FontPointer(font), GetPaintDevice());
		tlay.beginLayout();
		QTextLine tl = tlay.createLine();
		tlay.endLayout();
		int fit = su.size();
		int ui = 0;
		size_t i = 0;
		while (ui < fit) {
			const unsigned char uch = text[i];
			const unsigned int byteCount = UTF8BytesOfLead[uch];
			const int codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
			qreal xPosition = tl.cursorToX(ui + codeUnits);
			for (size_t bytePos = 0; (bytePos < byteCount) && (i < text.length()); bytePos++) {
				positions[i++] = xPosition;
			}
			ui += codeUnits;
		}
		XYPOSITION lastPos = 0;
		if (i > 0)
			lastPos = positions[i - 1];
		while (i < text.length()) {
			positions[i++] = lastPos;
		}
	}

	XYPOSITION SurfaceImpl::WidthTextUTF8(const Font* font, std::string_view text)
	{
		QFontMetricsF metrics(*FontPointer(font), device);
		QString su = QString::fromUtf8(text.data(), static_cast<int>(text.length()));
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
		return metrics.horizontalAdvance(su);
#else
		return metrics.width(su);
#endif
	}

	XYPOSITION SurfaceImpl::Ascent(const Font* font)
	{
		QFontMetricsF metrics(*FontPointer(font), device);
		return metrics.ascent();
	}

	XYPOSITION SurfaceImpl::Descent(const Font* font)
	{
		QFontMetricsF metrics(*FontPointer(font), device);
		// Qt returns 1 less than true descent
		// See: QFontEngineWin::descent which says:
		// ### we subtract 1 to even out the historical +1 in QFontMetrics's
		// ### height=asc+desc+1 equation. Fix in Qt5.
		return metrics.descent() + 1;
	}

	XYPOSITION SurfaceImpl::InternalLeading(const Font* /* font */)
	{
		return 0;
	}

	XYPOSITION SurfaceImpl::Height(const Font* font)
	{
		QFontMetricsF metrics(*FontPointer(font), device);
		return metrics.height();
	}

	XYPOSITION SurfaceImpl::AverageCharWidth(const Font* font)
	{
		QFontMetricsF metrics(*FontPointer(font), device);
		return metrics.averageCharWidth();
	}

	void SurfaceImpl::FlushCachedState()
	{
		if (device->paintingActive()) {
			GetPainter()->setPen(QPen());
			GetPainter()->setBrush(QBrush());
		}
	}

	void SurfaceImpl::FlushDrawing()
	{
	}

	QPaintDevice* SurfaceImpl::GetPaintDevice()
	{
		return device;
	}

	QPainter* SurfaceImpl::GetPainter()
	{
		Q_ASSERT(device);
		if (!painter) {
			if (device->paintingActive()) {
				painter = device->paintEngine()->painter();
			}
			else {
				painterOwned = true;
				painter = new QPainter(device);
			}

			// Set text antialiasing unconditionally.
			// The font's style strategy will override.
			painter->setRenderHint(QPainter::TextAntialiasing, true);

			painter->setRenderHint(QPainter::Antialiasing, true);
		}

		return painter;
	}

	std::unique_ptr<Surface> Surface::Allocate(Technology)
	{
		return std::make_unique<SurfaceImpl>();
	}

	//----------------------------------------------------------------------

	namespace {
		QWidget* window(WindowID wid) noexcept
		{
			return static_cast<QWidget*>(wid);
		}

		QRect ScreenRectangleForPoint(QPoint posGlobal)
		{
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
			const QScreen* screen = QGuiApplication::screenAt(posGlobal);
			if (!screen) {
				screen = QGuiApplication::primaryScreen();
			}
			return screen->availableGeometry();
#else
			const QDesktopWidget* desktop = QApplication::desktop();
			return desktop->availableGeometry(posGlobal);
#endif
		}
	}

	Window::~Window() noexcept = default;

	void Window::Destroy() noexcept
	{
		if (wid)
			delete window(wid);
		wid = nullptr;
	}
	PRectangle Window::GetPosition() const
	{
		// Before any size allocated pretend its 1000 wide so not scrolled
		return wid ? PRectFromQRect(window(wid)->frameGeometry()) : PRectangle(0, 0, 1000, 1000);
	}

	void Window::SetPosition(PRectangle rc)
	{
		if (wid)
			window(wid)->setGeometry(QRectFromPRect(rc));
	}

	void Window::SetPositionRelative(PRectangle rc, const Window* relativeTo)
	{
		QPoint oPos = window(relativeTo->wid)->mapToGlobal(QPoint(0, 0));
		int ox = oPos.x();
		int oy = oPos.y();
		ox += rc.left;
		oy += rc.top;

		const QRect rectDesk = ScreenRectangleForPoint(QPoint(ox, oy));
		/* do some corrections to fit into screen */
		int sizex = rc.right - rc.left;
		int sizey = rc.bottom - rc.top;
		int screenWidth = rectDesk.width();
		if (ox < rectDesk.x())
			ox = rectDesk.x();
		if (sizex > screenWidth)
			ox = rectDesk.x(); /* the best we can do */
		else if (ox + sizex > rectDesk.right())
			ox = rectDesk.right() - sizex;
		if (oy + sizey > rectDesk.bottom())
			oy = rectDesk.bottom() - sizey;
		if (oy < rectDesk.top())
			oy = rectDesk.top();

		Q_ASSERT(wid);
		window(wid)->move(ox, oy);
		window(wid)->resize(sizex, sizey);
	}

	PRectangle Window::GetClientPosition() const
	{
		// The client position is the window position
		return GetPosition();
	}

	void Window::Show(bool show)
	{
		if (wid)
			window(wid)->setVisible(show);
	}

	void Window::InvalidateAll()
	{
		if (wid)
			window(wid)->update();
	}

	void Window::InvalidateRectangle(PRectangle rc)
	{
		if (wid)
			window(wid)->update(QRectFromPRect(rc));
	}

	void Window::SetCursor(Cursor curs)
	{
		if (wid) {
			Qt::CursorShape shape;

			switch (curs) {
			case Cursor::text:  shape = Qt::IBeamCursor;        break;
			case Cursor::arrow: shape = Qt::ArrowCursor;        break;
			case Cursor::up:    shape = Qt::UpArrowCursor;      break;
			case Cursor::wait:  shape = Qt::WaitCursor;         break;
			case Cursor::horizontal: shape = Qt::SizeHorCursor; break;
			case Cursor::vertical:  shape = Qt::SizeVerCursor;  break;
			case Cursor::hand:  shape = Qt::PointingHandCursor; break;
			default:            shape = Qt::ArrowCursor;        break;
			}

			QCursor cursor = QCursor(shape);

			if (curs != cursorLast) {
				window(wid)->setCursor(cursor);
				cursorLast = curs;
			}
		}
	}

	/* Returns rectangle of monitor pt is on, both rect and pt are in Window's
	   window coordinates */
	PRectangle Window::GetMonitorRect(Point pt)
	{
		const QPoint posGlobal = window(wid)->mapToGlobal(QPoint(pt.x, pt.y));
		const QPoint originGlobal = window(wid)->mapToGlobal(QPoint(0, 0));
		QRect rectScreen = ScreenRectangleForPoint(posGlobal);
		rectScreen.translate(-originGlobal.x(), -originGlobal.y());
		return PRectFromQRect(rectScreen);
	}

	//----------------------------------------------------------------------
	class ListWidget : public QListWidget {
	public:
		explicit ListWidget(QWidget* parent);

		void setDelegate(IListBoxDelegate* lbDelegate);

		int currentSelection();

	protected:
		void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) override;
		void mouseDoubleClickEvent(QMouseEvent* event) override;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
		void initViewItemOption(QStyleOptionViewItem* option) const override;
#else
		QStyleOptionViewItem viewOptions() const override;
#endif

	private:
		IListBoxDelegate* delegate;
	};

	class ListBoxImpl : public ListBox {
	public:
		ListBoxImpl() noexcept;

		void SetFont(const Font* font) override;
		void Create(Window& parent, int ctrlID, Point location,
			int lineHeight, bool unicodeMode_, Technology technology) override;
		void SetAverageCharWidth(int width) override;
		void SetVisibleRows(int rows) override;
		int GetVisibleRows() const override;
		PRectangle GetDesiredRect() override;
		int CaretFromEdge() override;
		void Clear() noexcept override;
		void Append(char* s, int type) override;
		int Length() override;
		void Select(int n) override;
		int GetSelection() override;
		int Find(const char* prefix) override;
		std::string GetValue(int n) override;
		void RegisterImage(int type, const char* xpmData) override;
		void RegisterRGBAImage(int type, int width, int height,
			const unsigned char* pixelsImage) override;
		virtual void RegisterQPixmapImage(int type, const QPixmap& pm);
		void ClearRegisteredImages() override;
		void SetDelegate(IListBoxDelegate* lbDelegate) override;
		void SetList(const char* list, char separator, char typesep) override;
		void SetOptions(ListOptions options_) override;

		[[nodiscard]] ListWidget* GetWidget() const noexcept;
	private:
		bool unicodeMode{ false };
		int visibleRows{ 5 };
		QMap<int, QPixmap> images;
	};
	ListBoxImpl::ListBoxImpl() noexcept = default;

	void ListBoxImpl::Create(Window& parent,
		int /*ctrlID*/,
		Point location,
		int /*lineHeight*/,
		bool unicodeMode_,
		Technology)
	{
		unicodeMode = unicodeMode_;

		QWidget* qparent = static_cast<QWidget*>(parent.GetID());
		ListWidget* list = new ListWidget(qparent);

#if defined(Q_OS_WIN)
		// On Windows, Qt::ToolTip causes a crash when the list is clicked on
		// so Qt::Tool is used.
		list->setParent(nullptr, Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
			| Qt::WindowDoesNotAcceptFocus
#endif
		);
#else
		// On macOS, Qt::Tool takes focus so main window loses focus so
		// keyboard stops working. Qt::ToolTip works but its only really
		// documented for tooltips.
		// On Linux / X this setting allows clicking on list items.
		list->setParent(nullptr, static_cast<Qt::WindowFlags>(Qt::ToolTip | Qt::FramelessWindowHint));
#endif
		list->setAttribute(Qt::WA_ShowWithoutActivating);
		list->setFocusPolicy(Qt::NoFocus);
		list->setUniformItemSizes(true);
		list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
		list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
		list->move(location.x, location.y);

		int maxIconWidth = 0;
		int maxIconHeight = 0;
		foreach(QPixmap im, images) {
			if (maxIconWidth < im.width())
				maxIconWidth = im.width();
			if (maxIconHeight < im.height())
				maxIconHeight = im.height();
		}
		list->setIconSize(QSize(maxIconWidth, maxIconHeight));

		wid = list;
	}
	void ListBoxImpl::SetFont(const Font* font)
	{
		ListWidget* list = GetWidget();
		const FontAndCharacterSet* pfacs = AsFontAndCharacterSet(font);
		if (pfacs && pfacs->pfont) {
			list->setFont(*(pfacs->pfont));
		}
	}
	void ListBoxImpl::SetAverageCharWidth(int /*width*/) {}

	void ListBoxImpl::SetVisibleRows(int rows)
	{
		visibleRows = rows;
	}

	int ListBoxImpl::GetVisibleRows() const
	{
		return visibleRows;
	}
	PRectangle ListBoxImpl::GetDesiredRect()
	{
		ListWidget* list = GetWidget();
		int rows = Length();
		if (rows == 0 || rows > visibleRows) {
			rows = visibleRows;
		}
		int rowHeight = list->sizeHintForRow(0);
		int height = (rows * rowHeight) + (2 * list->frameWidth());

		QStyle* style = QApplication::style();
		int width = list->sizeHintForColumn(0) + (2 * list->frameWidth());
		if (Length() > rows) {
			width += style->pixelMetric(QStyle::PM_ScrollBarExtent);
		}

		return PRectangle(0, 0, width, height);
	}
	int ListBoxImpl::CaretFromEdge()
	{
		ListWidget* list = GetWidget();
		int maxIconWidth = 0;
		foreach(QPixmap im, images) {
			if (maxIconWidth < im.width())
				maxIconWidth = im.width();
		}

		int extra;
		// The 12 is from trial and error on macOS and the 7
		// is from trial and error on Windows - there may be
		// a better programmatic way to find any padding factors.
#ifdef Q_OS_DARWIN
		extra = 12;
#else
		extra = 7;
#endif
		return maxIconWidth + (2 * list->frameWidth()) + extra;
	}
	void ListBoxImpl::Clear() noexcept
	{
		ListWidget* list = GetWidget();
		list->clear();
	}
	void ListBoxImpl::Append(char* s, int type)
	{
		ListWidget* list = GetWidget();
		QString str = unicodeMode ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
		QIcon icon;
		if (type >= 0) {
			Q_ASSERT(images.contains(type));
			icon = images.value(type);
		}
		new QListWidgetItem(icon, str, list);
	}
	int ListBoxImpl::Length()
	{
		ListWidget* list = GetWidget();
		return list->count();
	}
	void ListBoxImpl::Select(int n)
	{
		ListWidget* list = GetWidget();
		QModelIndex index = list->model()->index(n, 0);
		if (index.isValid()) {
			QRect row_rect = list->visualRect(index);
			if (!list->viewport()->rect().contains(row_rect)) {
				list->scrollTo(index, QAbstractItemView::PositionAtTop);
			}
		}
		list->setCurrentRow(n);
	}
	int ListBoxImpl::GetSelection()
	{
		ListWidget* list = GetWidget();
		return list->currentSelection();
	}
	int ListBoxImpl::Find(const char* prefix)
	{
		ListWidget* list = GetWidget();
		QString sPrefix = unicodeMode ? QString::fromUtf8(prefix) : QString::fromLocal8Bit(prefix);
		QList<QListWidgetItem*> ms = list->findItems(sPrefix, Qt::MatchStartsWith);
		int result = -1;
		if (!ms.isEmpty()) {
			result = list->row(ms.first());
		}

		return result;
	}
	std::string ListBoxImpl::GetValue(int n)
	{
		ListWidget* list = GetWidget();
		QListWidgetItem* item = list->item(n);
		QString str = item->data(Qt::DisplayRole).toString();
		QByteArray bytes = unicodeMode ? str.toUtf8() : str.toLocal8Bit();
		return std::string(bytes.constData());
	}

	void ListBoxImpl::RegisterQPixmapImage(int type, const QPixmap& pm)
	{
		images[type] = pm;
		ListWidget* list = GetWidget();
		if (list) {
			QSize iconSize = list->iconSize();
			if (pm.width() > iconSize.width() || pm.height() > iconSize.height())
				list->setIconSize(QSize(qMax(pm.width(), iconSize.width()),
					qMax(pm.height(), iconSize.height())));
		}
	}

	void ListBoxImpl::RegisterImage(int type, const char* xpmData)
	{
		XPM xpmImage(xpmData);
		RGBAImage rgbaImage(xpmImage);
		RegisterRGBAImage(type, rgbaImage.GetWidth(), rgbaImage.GetHeight(), rgbaImage.Pixels());
	}

	void ListBoxImpl::RegisterRGBAImage(int type, int width, int height, const unsigned char* pixelsImage)
	{
		std::vector<unsigned char> imageBytes = ImageByteSwapped(width, height, pixelsImage);
		QImage image(&imageBytes[0], width, height, QImage::Format_ARGB32);
		RegisterQPixmapImage(type, QPixmap::fromImage(image));
	}

	void ListBoxImpl::ClearRegisteredImages()
	{
		images.clear();
		ListWidget* list = GetWidget();
		if (list)
			list->setIconSize(QSize(0, 0));
	}
	void ListBoxImpl::SetDelegate(IListBoxDelegate* lbDelegate)
	{
		ListWidget* list = GetWidget();
		list->setDelegate(lbDelegate);
	}
	void ListBoxImpl::SetList(const char* list, char separator, char typesep)
	{
		// This method is *not* platform dependent.
		// It is borrowed from the GTK implementation.
		Clear();
		size_t count = strlen(list) + 1;
		std::vector<char> words(list, list + count);
		char* startword = &words[0];
		char* numword = nullptr;
		int i = 0;
		for (; words[i]; i++) {
			if (words[i] == separator) {
				words[i] = '\0';
				if (numword)
					*numword = '\0';
				Append(startword, numword ? atoi(numword + 1) : -1);
				startword = &words[0] + i + 1;
				numword = nullptr;
			}
			else if (words[i] == typesep) {
				numword = &words[0] + i;
			}
		}
		if (startword) {
			if (numword)
				*numword = '\0';
			Append(startword, numword ? atoi(numword + 1) : -1);
		}
	}
	void ListBoxImpl::SetOptions(ListOptions)
	{
	}
	ListWidget* ListBoxImpl::GetWidget() const noexcept
	{
		return static_cast<ListWidget*>(wid);
	}

	ListBox::ListBox() noexcept = default;
	ListBox::~ListBox() noexcept = default;

	std::unique_ptr<ListBox> ListBox::Allocate()
	{
		return std::make_unique<ListBoxImpl>();
	}
	ListWidget::ListWidget(QWidget* parent)
		: QListWidget(parent), delegate(nullptr)
	{}

	void ListWidget::setDelegate(IListBoxDelegate* lbDelegate)
	{
		delegate = lbDelegate;
	}

	void ListWidget::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
		QListWidget::selectionChanged(selected, deselected);
		if (delegate) {
			const int selection = currentSelection();
			if (selection >= 0) {
				ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
				delegate->ListNotify(&event);
			}
		}
	}

	int ListWidget::currentSelection() {
		const QModelIndexList indices = selectionModel()->selectedRows();
		foreach(const QModelIndex ind, indices) {
			return ind.row();
		}
		return -1;
	}

	void ListWidget::mouseDoubleClickEvent(QMouseEvent* /* event */)
	{
		if (delegate) {
			ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
			delegate->ListNotify(&event);
		}
	}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
	void ListWidget::initViewItemOption(QStyleOptionViewItem* option) const
	{
		QListWidget::initViewItemOption(option);
		option->state |= QStyle::State_Active;
	}
#else
	QStyleOptionViewItem ListWidget::viewOptions() const
	{
		QStyleOptionViewItem result = QListWidget::viewOptions();
		result.state |= QStyle::State_Active;
		return result;
	}
#endif
	//----------------------------------------------------------------------
	Menu::Menu() noexcept : mid(nullptr) {}
	void Menu::CreatePopUp()
	{
		Destroy();
		mid = new QMenu();
	}

	void Menu::Destroy() noexcept
	{
		if (mid) {
			QMenu* menu = static_cast<QMenu*>(mid);
			delete menu;
		}
		mid = nullptr;
	}
	void Menu::Show(Point pt, const Window& /*w*/)
	{
		QMenu* menu = static_cast<QMenu*>(mid);
		menu->exec(QPoint(pt.x, pt.y));
		Destroy();
	}

	//----------------------------------------------------------------------

	ColourRGBA Platform::Chrome()
	{
		QColor c(Qt::gray);
		return ColourRGBA(c.red(), c.green(), c.blue());
	}

	ColourRGBA Platform::ChromeHighlight()
	{
		QColor c(Qt::lightGray);
		return ColourRGBA(c.red(), c.green(), c.blue());
	}

	const char* Platform::DefaultFont()
	{
		static char fontNameDefault[200] = "";
		if (!fontNameDefault[0]) {
			QFont font = QApplication::font();
			strcpy(fontNameDefault, font.family().toUtf8());
		}
		return fontNameDefault;
	}

	int Platform::DefaultFontSize()
	{
		QFont font = QApplication::font();
		return font.pointSize();
	}

	unsigned int Platform::DoubleClickTime()
	{
		return QApplication::doubleClickInterval();
	}

	void Platform::DebugDisplay(const char* s) noexcept
	{
		qWarning("Scintilla: %s", s);
	}

	void Platform::DebugPrintf(const char* format, ...) noexcept
	{
		char buffer[2000];
		va_list pArguments{};
		va_start(pArguments, format);
		vsnprintf(buffer, std::size(buffer), format, pArguments);
		va_end(pArguments);
		Platform::DebugDisplay(buffer);
	}

	bool Platform::ShowAssertionPopUps(bool /*assertionPopUps*/) noexcept
	{
		return false;
	}

	void Platform::Assert(const char* c, const char* file, int line) noexcept
	{
		char buffer[2000];
		snprintf(buffer, std::size(buffer), "Assertion [%s] failed at %s %d", c, file, line);
		if (Platform::ShowAssertionPopUps(false)) {
			QMessageBox mb("Assertion Failure", buffer, QMessageBox::NoIcon,
				QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton);
			mb.exec();
		}
		else {
			strcat(buffer, "\n");
			Platform::DebugDisplay(buffer);
		}
	}
}